package cz.muni.fi.pb138.avradiation.web;

import cz.muni.fi.pb138.avradiation.Sensor.Sensor;
import cz.muni.fi.pb138.avradiation.export.xml.XMLexport;
import cz.muni.fi.pb138.avradiation.export.xml.XMLexportImlp;
import cz.muni.fi.pb138.avradiation.parser.CSVParser;
import cz.muni.fi.pb138.avradiation.parser.Parser;
import cz.muni.fi.pb138.avradiation.visualisation.chart.ChartXY;
import cz.muni.fi.pb138.avradiation.visualisation.chart.ChartXYImpl;
import cz.muni.fi.pb138.avradiation.visualisation.map.ImageMap;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 *
 * @author Andrej Kuročenko <kurochenko@gmail.com>
 */
@WebServlet(name = "VisualisationServlet", urlPatterns = {
    "/index", "/drawMap", "/drawPlot", "/drawMultiPlot", "/upload", "/exportXML"
})
public class VisualisationServlet extends HttpServlet {
    
    private static final Logger logger = Logger.getLogger(
         VisualisationServlet.class.getName());
    
    private static final int BUF_SIZE = 1000;
    private static final String EXPORTXML_FILENAME = "export.xml";

    private static final String JSP_FOLDER = "WEB-INF/jsp/";
    public static final String DEF_TITLE = "AVRadiation";
    private static final int DEF_IMG_WIDTH = 900;
    private static final int DEF_IMG_HEIGHT = 500;
    private SensorManagerImpl sensorManager = new SensorManagerImpl();
    private LocationManagerImpl locationManager = new LocationManagerImpl();

    @Resource(name = "jdbc/avmgr")
    void setDataSource(DataSource ds) {
        sensorManager.setDataSource(ds);
        locationManager.setDataSource(ds);
    }

    /**
     * Checks whether passed string is null or empty
     * 
     * @param s 
     * @return true if <code>s</code> is null or empty (trim is applied befor checking)
     */
    private boolean isEmpty(String s) {
        return (s == null || s.trim().isEmpty());
    }

    /**
     * Checks whether ID in string is correct contact list ID from database
     * 
     * @param s
     * @return true if ID is correct, false otherwise
     */
    private boolean isCorrectID(String s) {
        if (isEmpty(s)) {
            return false;
        }
        Long id = Long.valueOf(s);
        return id >= 0l || locationManager.findLocationById(id) != null;
    }

    private String createSensorGroupName(String sensorName) {
        return sensorName.replaceAll("[0-9\\.%]*$", "");
    }

    /**
     * Manages index page rendering
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void renderIndex(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute("title", "Visualisation of measured data");

        String page = request.getParameter("page");
        request.setAttribute("page", ((page == null) ? "locations" : page));


        if (request.getParameter("location") != null) {
            request.setAttribute("page", "sensors");
            if (isCorrectID(request.getParameter("location"))) {
                renderSensorsGroup(request, response, Long.valueOf(request.getParameter("location")));
            } else {
                request.setAttribute("error", "Wrong location ID '"
                        + request.getParameter("location") + "'");
            }
        } else {
            renderLocations(request, response);
        }

        request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
    }

    /**
     * Handles visualization of upload page
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void renderUpload(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute("title", "Upload of CSV files");
        request.setAttribute("hint", "Choose CSV file to upload. Please notice, "
                + "that upload and parsing operation may take some time (even several minutes).");

        request.setAttribute("page", "upload");

        if (request.getMethod().equals("POST")) {
            handleCSVParse(request, response);
        } else {
            request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
        }
    }

    /**
     * Handles visualization of sensors Groups and visualization elements
     * such as plots, maps etc.
     * 
     * @param request
     * @param response
     * @param location
     * @throws ServletException
     * @throws IOException 
     */
    private void renderSensorsGroup(HttpServletRequest request, HttpServletResponse response, Long location)
            throws ServletException, IOException {

        request.setAttribute("mdate", new TreeSet<String>(sensorManager.getMeasureDays()));

        request.setAttribute("hint", "Sensors are groupped by their names. "
                + "You can select whole group or click on the goups name "
                + "to show sensors and then select required sensor. "
                + "After you select any sensor or whole group, choose type of visualisation.");


        SortedMap<String, SortedSet<Sensor>> sensorGroups =
                new TreeMap<String, SortedSet<Sensor>>();

        String groupName = null;
        for (Sensor sensor : sensorManager.getSensorsByLocation(location)) {
            groupName = createSensorGroupName(sensor.getName());
            if (!sensorGroups.containsKey(groupName)) {
                sensorGroups.put(groupName, new TreeSet<Sensor>());
            }
            sensorGroups.get(groupName).add(sensor);
        }

        if (request.getMethod().equals("POST") && request.getParameterValues("sensors") != null) {
            
            List<String> toVisualize = Arrays.asList(request.getParameterValues("sensors"));
            
            String sensorListID = UUID.randomUUID().toString();
            request.getSession().setAttribute(sensorListID, toVisualize);
            request.setAttribute("sensorListIDObj", sensorListID);
            
            
            request.setAttribute("toVisualize", toVisualize);
        }

        request.setAttribute("groups", sensorGroups);
    }

    private void renderLocations(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute("hint", "Choose location whose sensors you want to visualise.");
        request.setAttribute("title", "Sensors locations");
        request.setAttribute("locations", new TreeSet<Location>(locationManager.getAllLocations()));

    }

    /**
     * Draws map according to sensor ID/IDs
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void drawMap(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Long sensorID = Long.valueOf(request.getParameter("sensorID"));
        String startTime = request.getParameter("from");
        String endTime = request.getParameter("to");

        response.setContentType("image/png");
        OutputStream out = response.getOutputStream();
        ImageIO.write(ImageMap.createImage(DEF_IMG_HEIGHT, 
                sensorManager.getMeasuredValuesByTimeAndSensor(sensorID, startTime, endTime)), "png", out);
        out.close();

    }

    /**
     * Draws plot with single sensor
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void drawPlot(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        Long sensorID = Long.valueOf(request.getParameter("sensorID"));
        String startTime = request.getParameter("from");
        String endTime = request.getParameter("to");

        ChartXY chart = new ChartXYImpl();

        response.setContentType("image/png");
        OutputStream out = response.getOutputStream();
        ImageIO.write(chart.drawChart(sensorManager.getMeasuredValuesByTimeAndSensor(sensorID, startTime, endTime), 
                DEF_IMG_WIDTH, DEF_IMG_HEIGHT), "png", out);
        out.close();

    }

    /**
     * Draws plot with multiple sensors
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void drawMultiPlot(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        List<String> sensorIDs = 
                (List<String>) request.getSession().getAttribute(request.getParameter("sensorID"));   
        String startTime = request.getParameter("from");
        String endTime = request.getParameter("to");

        ChartXY chart = new ChartXYImpl();

        response.setContentType("image/png");
        OutputStream out = response.getOutputStream();
        ImageIO.write(chart.drawChart(sensorManager.getSensorsByIdAndDateCollection(sensorIDs, startTime, endTime), 
                DEF_IMG_WIDTH, DEF_IMG_HEIGHT), "png", out);
        out.close();

    }

    /**
     * Handles parsing operation of CSV file, interacts with user
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void handleCSVParse(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {

        //File uploadedFile;
        if (ServletFileUpload.isMultipartContent(request)) {
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            File uploadedFile;
            Parser parser = new CSVParser();
            try {
                List items = upload.parseRequest(request);
                Iterator iterator = items.iterator();
                while (iterator.hasNext()) {
                    FileItem item = (FileItem) iterator.next();
                    if (!item.isFormField()) {
                        String fileName = item.getName();
                        String root = getServletContext().getRealPath("/");
                        File path = new File(root + "/uploads");
                        if (!path.exists()) {
                            boolean status = path.mkdirs();
                        }
                        uploadedFile = new File(path + "/" + fileName);

                        item.write(uploadedFile);
                        sensorManager.storeSensors(parser.parse(uploadedFile));
                        request.setAttribute("message", "Upload of CSV file " 
                                + uploadedFile.getName() + " successful");
                        request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
                    }
                }

            } catch (FileUploadException ex) {
                request.setAttribute("error", "File failed to upload");
                request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
            } catch (Exception ex) {
                request.setAttribute("error", "File failed to upload. Maybe not CSV file");
                request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
            }
        } else {
            request.setAttribute("error", "File is not multiparted");
            request.getRequestDispatcher(JSP_FOLDER + "index.jsp").forward(request, response);
        }
    }
    
    /**
     * Handles export of XML file according to sensors
     * 
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException 
     */
    private void exportXML(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        XMLexport xml = new XMLexportImlp();
        List<String> sensorIDs = 
                (List<String>) request.getSession().getAttribute(request.getParameter("sensorID"));

        String startTime = request.getParameter("from");
        String endTime = request.getParameter("to");
        try {
            try {
                xml.exportToXML(new URI(EXPORTXML_FILENAME), 
                        sensorManager.getSensorsByIdAndDateCollection(sensorIDs, startTime, endTime));
            } catch (URISyntaxException ex) {
                logger.log(Level.SEVERE, "Wrong URI name", ex);
            }
        } catch (ParserConfigurationException ex) {
            logger.log(Level.SEVERE, "Wrong parser configuration", ex);
        } catch (TransformerConfigurationException ex) {
            logger.log(Level.SEVERE, "Wrong transformer configuration", ex);
        } catch (TransformerException ex) {
            logger.log(Level.SEVERE, "Error while transformation", ex);
        }
        
        File f = new File(EXPORTXML_FILENAME);
        int length = 0;
        ServletOutputStream op = response.getOutputStream();
        ServletContext context  = getServletConfig().getServletContext();
        String mimetype = context.getMimeType(f.getName());


        response.setContentType( (mimetype != null) ? mimetype : "application/octet-stream" );
        response.setContentLength( (int)f.length() );
        response.setHeader( "Content-Disposition", "attachment; filename=\""+EXPORTXML_FILENAME+"\"" );

        byte[] bbuf = new byte[BUF_SIZE];
        DataInputStream in = new DataInputStream(new FileInputStream(f));

        while ((in != null) && ((length = in.read(bbuf)) != -1))
        {
            op.write(bbuf,0,length);
        }

        in.close();
        op.flush();
        op.close();
    }

    /** 
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=UTF-8");

        if (request.getServletPath().equals("/index")) {
            renderIndex(request, response);
        } else if (request.getServletPath().equals("/drawMap")) {
            drawMap(request, response);
        } else if (request.getServletPath().equals("/drawPlot")) {
            drawPlot(request, response);
        } else if (request.getServletPath().equals("/drawMultiPlot")) {
            drawMultiPlot(request, response);
        } else if (request.getServletPath().equals("/exportXML")) {
            exportXML(request, response);
        } else if (request.getServletPath().equals("/upload")) {
            renderUpload(request, response);
        } else if (request.getServletPath().equals("/parseCSV")) {
            handleCSVParse(request, response);
        } else {
            throw new RuntimeException("Unknown operation: "
                    + request.getServletPath());
        }
    }

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>
}
